C++ on MSVC講習/反復文 - while ( ほわいる ) /for ( ふぉー ) 文
あらすじと概要
前回は条件分岐2回目ということで、switch文を解説しました。 今回は、同じような動作を反復することで、短いコードに収める文たちを解説します。
重要語
goto文
実行すると、対応するラベルに処理が移動する文
ラベル
switch文やgoto文などから移動する時の目印
ループ
同じ文を複数回実行する部分
複合代入演算子
@
を二項演算子として、a @= b
の形で、a = a @ b
を行う演算子
+=
複合加算代入演算子
-=
複合減算代入演算子
*=
複合乗算代入演算子
/=
複合除算代入演算子
%=
複合剰余代入演算子
while文
条件
がtrueである限り文
を実行し続ける文
do文(do-while文とも)
無条件に1回文
を実行してから、条件式
がtrueである限り文
を実行し続ける文
for文
初期化文
と反復式
が追加されて使いやすいwhile文
break文
switch文、while文、do文、for文を終了させる文
continue文
while文、do文、for文の文
実行を終了させる文
ループカウンタ
ループでループの回数を数える時に使っている変数
++
インクリメント演算子、整数の変数の値を1
増やす
--
デクリメント演算子、整数の変数の値を1
減らす
goto文
goto文は、C++よりもより低いレイヤーでも存在する非常に機械よりの文です。 switch文で触れたように、goto文を実行すると、対応するラベルに処理が移動します。
goto文
COPY
#include < iostream>
int main()
{
goto A;
std:: cout << "飛ばされる" ;
A:
int a = 0 ;
BACK:
{
std:: cout << a << "\n" ;
a += 1 ;
}
if ( a < 10 ) goto BACK;
}
解説
解説です。
ラベル
ラベル名
と: ( ころん ) を1個以上文の頭に付けて文をラベル付けすることが出来ます。 ラベルはgoto文から移動する先の文を示し、順次実行時には何も起こしません。 goto文で使用するラベルは、識別子のルールに従って命名する必要があります。 ラベルは唯一、関数単位でのみスコープを持っており、他の識別子とは別で管理されます。 つまり、「同じ関数の中」でのみ、同じラベルを複数の文に付けてはいけないのです。
goto文の効果
goto文は、以下のように記述し、指定したラベルへと直接処理を移します。 つまり、実行されたgoto文から指定したラベルの間にある文は、実行されないのです。 ただし、goto文は同じ関数内でのみ処理を移すことが出来、関数を超えることは出来ません。
goto文の制限
直接処理を移してしまうgoto文は非常に強力ですが、多用はコードが読みづらくなります。 それはさておき、goto文は変数のスコープに入ってはいけないという制限もあります。 つまり、ある変数の宣言を実行しないまま、その変数が存在する文に入ってはいけないのです。 例えばgoto Z; { int i = 0; Z:; }
だと、宣言を飛び越えているのでダメです。
goto文の制限の例外
ただし、今説明できないものを含め、いくつかの場合にはその制限が無いことがあります。 例えば、組み込み型の場合において初期化子
がない場合は制限が適用されません。 ただ、goto文を使った方が簡潔になる機会がほぼ無いので、必ず制限を守ればいいでしょう。
goto文の制限の逆
さて、制限は変数のスコープに入る事でしたが、逆に出る時はどうなるのでしょうか。 変数のスコープから出る場合は、ライフタイムが終了した変数を適切に処理してくれます。 言い換えれば、通常通りに変数のスコープから抜けた時と同様の後処理をしてくれます。 特に気にする必要はありません。
goto文とif文でループ
同じ文を複数回実行する部分のことをループと言い、while/for文はループの文です。 goto文とif文を用いると、良いものでは無いですが、while/for文と同じことが出来ます。 サンプルコードのように、ラベルを置いて、if文でgoto文を包んでループ出来ます。 サンプルコードでは、変数a
を1周ごとに1
増やしていて、0
~9
の間ループしますね。
複合代入演算子
さて、a += 1;
がa
に1
足しているであろうことは分かると思います。 この+=
などの=
の前に何らかの二項演算子がくっついたものを、複合代入演算子と言います。 右辺の値を左辺に「何らかの二項演算子」で処理して左辺に代入するのです。 なので、a += 1;
は、a = a + 1;
と同じことで、a
に1
足した物をa
に代入しています。 即ち、a
に1
足している、ということですね。
複合代入演算子(今までに解説した二項演算子の中で存在するもの)
+=
加算代入
-=
減算代入
*=
乗算代入
/=
除算代入
%=
剰余代入
while文
while文は、先述の通り、ループを書くための文です。 goto文とif文を使うより単純に書くことが出来ます。
while文
COPY
#include < iostream>
#include < string>
#include < algorithm>
int main()
{
int n; std:: cin >> n;
while ( n /= 10 )
{
std:: cout << n << "\n" ;
}
std:: cout << "\n" ;
while ( std:: cin >> n)
{
if ( n > - 10 ) { std:: cout << "next : " ; continue ; }
if ( n < 20 ) { break ; }
}
std:: cout << "\n" ;
std:: string s = "abc" ;
do
{
std:: cout << s << "\n" ;
} while ( std:: next_permutation( s.begin(), s.end()));
}
実行結果例1
COPY
$ 1 $ -20 abc acb bac bca cab cba
実行結果例2
COPY
$ 11 1 $ 0 $ next : -10 abc acb bac bca cab cba
実行結果例3
COPY
$ 11111111 1111111 111111 11111 1111 111 11 1 $ 50 $ next : -50 abc acb bac bca cab cba
解説
解説です。
while ( ほわいる ) 文とdo ( どぅー ) 文(do-while文)
while文は、条件
がtrueである間、文
を実行し続ける文です。 do文は、文
が無条件で1度実行された後、while文と同じ動作になる文です。 do文は規格での表記に従っていますが、普通はdo-while文などと呼ばれます。
while文
COPY
while
(
条件 )
文
do文
COPY
do
文 while
(
条件式 )
;
条件
条件
はif文と同様、boolに評価できる式や、boolに変換できる型の変数宣言が出来ます。条件
で宣言された変数は、次に条件
が評価される時には破棄され、また宣言されます。 なお、条件
で宣言された変数は、やはりwhile文全体をスコープに持ちます。
条件式
と文
一方で、条件式
は名前の通り、boolに変換できる式のみを書け、宣言は書けません。 そして、文
は、if文やswitch文などと同様に、やはり1つの文で、複合文がほとんどです。
while文とdo文の実行のされ方
while文とdo文は、その動きを複合文とif文とgoto文に置き換えることで説明出来ます。 次のような模擬コード(構文の名前はこれ
で表記)に置き換えることが出来ます。
while文を置き換えると(模擬コード)
COPY
label: { if ( 条件
) { 文
goto label; } }
do文を置き換えると(模擬コード)
COPY
label: { 文
if ( 条件式
) goto label; }
break ( ぶれいく ) 文、continue ( こんてぃにゅー ) 文
break文はswitch文の時と同様に、while/do文でもwhile/do文を終了させます。 continue文は、実行されるとループの最後へと実行を移し、文
の実行が終了します。 ただし両方とも、while文がネストしている時には、一番内側のwhile文を終了させます。
break文とcontinue文を疑似的に表現すると
break/continue文の移動先をbreak/continueラベルと、疑似的に表現して、 先程の疑似コードに組み込んだものと、while/do文に組み込んだものは以下です。
while文のbreak/continue文の行先(模擬コード)
COPY
label: { if ( 条件
) { 文
continue: goto label; } } break:
do文のbreak/continue文の行先(模擬コード)
COPY
label: { 文
continue: if ( 条件式
) goto label; } break:
while文で表現すると(模擬コード)
COPY
while ( 条件
) { 文
continue: } break:
do文で表現すると(模擬コード)
COPY
do { 文
continue: } while ( 条件式
) ; break:
1つ目のwhile
1つ目のwhile文は、入力した整数をどんどん10
で割って、0
になると終了します。 つまり、入力した整数の桁数-1
がループする回数になるということですね。
std::cinがboolに評価出来るということ
std::cinがboolに評価できる事に驚いた人もいるかもしれません。 std::cinは、入力が出来る時にtrue、入力が出来ないときにfalseに評価されます。 今は標準入力の内容は分かっていると思うので、チェックする必要は無いでしょう。
条件どうなってるの
2つ目のwhile文の中のif文は、一見すると分かりづらくなっています。 が、よくよく整理すると次のようになり、とても簡単ですね。
2つ目のwhile文
n >= -9
continue
n <= -10
break
std::next_permutation
std::next_permutation
は、順列を生成するアルゴリズムが実装された関数です。#include <algorithm>
を書くと使うことが出来るようになります。 詳しくは今後ですが、現在までに解説した型で使用できるのは、std::stringだけです。 今回のサンプルコードでの効果は、s
の中身が、辞書順で次の順列に書き換えられます。
順列とは
順列は数学で学ぶ考え方ですが、異なるいくつかのものを順序付けして並べることです。 例えば、1,2,3
という列があれば、これの並べ方は以下のように6
通りあります。1,2,3
/1,3,2
/2,1,3
/2,3,1
/3,1,2
/3,2,1
ここで、この6
通りは辞書順に並んでいますが、1つ次(右)の列(一番右なら一番左へ) に変えるのがstd::next_permutation
のすることだ、という風に言うことが出来ます。
for文
for文は、whileの上位互換です。更に簡潔に書くことが出来ます。
for文
COPY
#include < iostream>
int main()
{
int n;
for (; std:: cin >> n;)
{
if ( n > 0 ) break ;
std:: cout << "自然数(1以上の整数)を入力してください\n" ;
}
for ( int i = 0 ; i < n; ++ i)
{
std:: cout << i << " " ;
continue ;
}
std:: cout << "\n" ;
for ( int i = 1 ; i < 10 ; ++ i)
{
for ( int j = 1 ; j < 10 ; j++)
{
if ( i * j > 30 ) break ;
std:: cout << i * j << "\t" ;
}
std:: cout << "\n" ;
}
}
実行結果例
COPY
$ 30 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 5 10 15 20 25 30 6 12 18 24 30 7 14 21 28 8 16 24 9 18 27
解説
解説にいきます。
for文
for文は、while文に初期化文
と反復式
を付け加えたものです。 やはり条件
がtrueである間、文
を実行し続ける文です。 基本的にはwhile文と似ていますが、do-for文みたいなのはありません。
for文
COPY
for
(
初期化文 条件 ;
反復式 )
文
正確さを無くしてわかりやすくしたfor文
COPY
for
(
宣言か式 ;
宣言か式 ;
式 )
文
初期化文
初期化文
はif文やswitch文で出てきたもので、むしろfor文が元です。;
で終わる変数などの宣言、式文や;
だけの文である空文などが書けます。 そして、やはりここで宣言された変数は、for文全体をスコープに持ちます。
条件
、文
条件
もif文やwhile文で出てきたものと全く同じものになっています。 boolに評価できる式や、boolに変換することが出来る型の変数の宣言が書けます。 また、条件
で宣言された変数も、やはりfor文全体をスコープに持ちます。 ただし、for文の条件
は省略すると、trueとして扱われる事には注意しましょう。文
も、やはり今までのものと同様で、複合文が好まれて使われます。
反復式
とfor文の実行のされ方
反復式
には、任意の式を書くことが出来ます。反復式
は文
が実行された後、条件
が評価される前に評価されます。 つまり、for文は以下のような疑似コードで表現することが出来ます。
for文をwhile文で表現すると(疑似コード)
COPY
{ 初期化文
while ( 条件
) { 文
反復式
; } }
for文をgoto文レベルで表現すると(疑似コード)
COPY
{ 初期化文
label: { if ( 条件
) { 文
反復式
; goto label; } } }
break文、continue文
やっぱり、break文とcontinue文もwhile文と同じ挙動を示します。 break文はfor文を終わらせ、continue文はループの最後へ移動、文
を終了させます。 ただし、continue文が実行された時も、反復式
は実行されることに留意しましょう。 また、for文がネストしていたら、一番内側のfor文を終了させるのも同じです。
break文とcontinue文を疑似的に表現すると
break/continue文の移動先をbreak/continueラベルと、疑似的に表現して、 先程の疑似コードに組み込んだものと、for文に組み込んだものは以下です。
for文のbreak/continue文の行先1(疑似コード)
COPY
for ( 初期化文
条件
; 反復式
) { 文
continue: } break:
for文のbreak/continue文の行先2(疑似コード)
COPY
{ 初期化文
while ( 条件
) { 文
continue: 反復式
; } break: }
for文のbreak/continue文の行先3(疑似コード)
COPY
{ 初期化文
label: { if ( 条件
) { 文
continue: 反復式
; goto label; } } break: }
ループカウンタとイン/デクリメント
初期化文
で宣言し、ループを制御する変数を、ループカウンタと呼ぶことがあります。 ほとんどの場合、ループカウンタは整数型の変数で、識別子にi
が使われます。 そして、反復式
ではループカウンタに1
を足し引きすることが多いので、演算子があります。1
足すのがインクリメント演算子、1
引くのがデクリメント演算子で、前置と後置があります。 後置のイン/デクリメント演算子は、落とし穴があるので、単独の式で使用するべきでしょう。
インクリメントとデクリメント演算子(整数の変数をnとして)
++n
前置インクリメント、n += 1
と同じ
n++
後置インクリメント、n += 1
と類似
--n
前置デクリメント、n -= 1
と同じ
n--
後置デクリメント、n -= 1
と類似
1つ目のfor文
int n;
は、このfor文に入れてしまうと、後のfor文で使えないので前に書きます。std::cin >> n
を条件
に置いています。反復式
に置くとマズいのがわかるでしょうか。反復式
に置くと、変数n
を未初期化で読み取ってしまうので、未定義動作になりますね。
2つ目のfor文(for文でn
回ループしたい)
for文は、初期化文
と反復式
を持っていることから、n
回ループしたい時によく使います。 特に、0
からn-1
のn
回ループすることが往々にしてあるため、書き方に慣れましょう。 continue文が申し訳程度にありますが、continue文の後に何もないので意味はないです。
3/4つ目のfor文(2重ループしたい)
for文の中にfor文を入れれば、ループさせる回数を掛け算のように増やすことが出来ます。 両方のfor文で、ループカウンタを1から9まで回せば、九九表を作ることが出来ますね。 2重ループする時には、外側と内側のループカウンタの名前を被らせないようにしましょう。 被らせると、整数と小数の回で解説したように、外側のループカウンタが隠れてしまいます。 なお、今回は30よりも大きい値は表示しないようにしたので、右下が消えています。
練習問題
今回は、フィボナッチ数列に関した問題にしましょう。
問題文
フィボナッチ数列のn番目の項は何ですか。 フィボナッチ数列とは、1, 1, 2, 3, 5, 8, 13, 21, 34
のように、 第1項、第2項が1
で、それ以外の項は1
/2
個前の項の和であるような、言い換えれば、fib(1) = 1, fib(2) = 1, fib(N) = fib(N-1) + fib(N-2)
である数列のことです。
制約
COPY
1 <= N <= 93, Nは整数
入出力例3
COPY
$ 50 12586269025
入出力例4
COPY
$ 93 12200160415121876738
入出力例の注意
出力すべき値は、intを超えるような値になることがあります。 また、long longをも超えるような値になることがあります。
ヒント1
使う変数は、入力用変数、ループカウンタ、それに加えて少なくとも2つ変数が必要です。 回答例では、入力用変数、ループカウンタ、それに加えて3つの変数を使用しています。
ヒント2
現在の項と1つ前の項を保持する変数を用意してループしてもいいですね。 あるいは、答え変数と2つの変数を用意して、現在の項をとりあえず答え変数にいれてしまい、 そのあと、2つの変数の小さい方(古い方)に代入してしまってループするのでもいいですね。
回答例
回答例
COPY
#include < iostream>
int main()
{
int n;
std:: cin >> n;
unsigned long long a = 1 , b = 1 , ans = 1 ;
for ( int i = 2 ; i < n; i++)
{
ans = a + b;
if ( i % 2 == 0 )
{
a = ans;
}
else
{
b = ans;
}
}
std:: cout << ans << "\n" ;
}
N番目の値が何かの一覧
N番目の値が何かの一覧
COPY
1 1 2 1 3 2 4 3 5 5 6 8 7 13 8 21 9 34 10 55 11 89 12 144 13 233 14 377 15 610 16 987 17 1597 18 2584 19 4181 20 6765 21 10946 22 17711 23 28657 24 46368 25 75025 26 121393 27 196418 28 317811 29 514229 30 832040 31 1346269 32 2178309 33 3524578 34 5702887 35 9227465 36 14930352 37 24157817 38 39088169 39 63245986 40 102334155 41 165580141 42 267914296 43 433494437 44 701408733 45 1134903170 46 1836311903 47 2971215073 48 4807526976 49 7778742049 50 12586269025 51 20365011074 52 32951280099 53 53316291173 54 86267571272 55 139583862445 56 225851433717 57 365435296162 58 591286729879 59 956722026041 60 1548008755920 61 2504730781961 62 4052739537881 63 6557470319842 64 10610209857723 65 17167680177565 66 27777890035288 67 44945570212853 68 72723460248141 69 117669030460994 70 190392490709135 71 308061521170129 72 498454011879264 73 806515533049393 74 1304969544928657 75 2111485077978050 76 3416454622906707 77 5527939700884757 78 8944394323791464 79 14472334024676221 80 23416728348467685 81 37889062373143906 82 61305790721611591 83 99194853094755497 84 160500643816367088 85 259695496911122585 86 420196140727489673 87 679891637638612258 88 1100087778366101931 89 1779979416004714189 90 2880067194370816120 91 4660046610375530309 92 7540113804746346429 93 12200160415121876738
参照、出典
参照や出典です
参照
[stmt.label]
https://timsong-cpp.github.io/cppwp/n4861/stmt.label
[stmt.jump]
https://timsong-cpp.github.io/cppwp/n4861/stmt.jump#stmt.goto
[stmt.iter]
https://timsong-cpp.github.io/cppwp/n4861/stmt.iter
文 - cppreference.com
https://ja.cppreference.com/w/cpp/language/statements
goto 文 - cppreference.com
https://ja.cppreference.com/w/cpp/language/goto
代入演算子 - cppreference.com
https://ja.cppreference.com/w/cpp/language/operator_assignment
while ループ - cppreference.com
https://ja.cppreference.com/w/cpp/language/while
std::basic_ios<CharT,Traits>::operator bool - cppreference.com
https://ja.cppreference.com/w/cpp/io/basic_ios/operator_bool
for ループ - cppreference.com
https://ja.cppreference.com/w/cpp/language/for
(C) 2020 駒場東邦物理部[KTPC]